﻿using System;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Ewk.Math.Algebra.UnitTests
{
    [TestClass]
    public class VectorDoubleUnitTests : VectorUnitTestsBase<double>
    {
        #region Add
        [TestMethod]
        public void VectorDouble_Add_performs_an_add_to_each_element()
        {
            var vector1 = CreateVector(1, 2, 3);
            const int factor = 2;

            vector1.Add(factor);

            Assert.AreEqual(3.0, vector1[0].Value);
            Assert.AreEqual(4.0, vector1[1].Value);
            Assert.AreEqual(5.0, vector1[2].Value);
        }

        [TestMethod]
        public void VectorDouble_Add_performs_an_of_two_vectors()
        {
            var vector1 = CreateVector(1, 2, 3);
            var vector2 = CreateVector(1, 2, 3);

            vector1.Add(vector2);

            Assert.AreEqual(2.0, vector1[0].Value);
            Assert.AreEqual(4.0, vector1[1].Value);
            Assert.AreEqual(6.0, vector1[2].Value);
        }
        #endregion

        #region Subtract
        [TestMethod]
        public void VectorDouble_Subtract_performs_an_add_to_each_element()
        {
            var vector1 = CreateVector(1, 2, 3);
            const int factor = 2;

            vector1.Subtract(factor);

            Assert.AreEqual(-1.0, vector1[0].Value);
            Assert.AreEqual(0.0, vector1[1].Value);
            Assert.AreEqual(1.0, vector1[2].Value);
        }

        [TestMethod]
        public void VectorDouble_Subtract_performs_a_subtracts_of_two_vectors()
        {
            var vector1 = CreateVector(1, 2, 3);
            var vector2 = CreateVector(1, 2, 3);

            vector1.Subtract(vector2);

            Assert.AreEqual(0.0, vector1[0].Value);
            Assert.AreEqual(0.0, vector1[1].Value);
            Assert.AreEqual(0.0, vector1[2].Value);
        }
        #endregion

        #region Multiply
        [TestMethod]
        public void VectorDouble_Multiply_performs_a_multiplication_on_each_element()
        {
            var vector1 = CreateVector(1, 2, 3);
            const int factor = 2;

            vector1.Multiply(factor);

            Assert.AreEqual(2.0, vector1[0].Value);
            Assert.AreEqual(4.0, vector1[1].Value);
            Assert.AreEqual(6.0, vector1[2].Value);
        }

        [TestMethod]
        public void VectorDouble_DotProduct_performs_a_multiplication_on_each_element()
        {
            var vector1 = CreateVector(1, 2, 3);
            var vector2 = CreateVector(1, 2, 3);

            var dotProduct = Vector<double>.DotProduct(vector1, vector2);

            Assert.AreEqual(14.0, dotProduct.Value);
        }

        [TestMethod]
        public void VectorDouble_CrossProduct_performs_a_multiplication_on_each_element()
        {
            var vector1 = CreateVector(1, 0, 0);
            var vector2 = CreateVector(0, 2, 0);

            var crossProduct = Vector<double>.CrossProduct(vector1, vector2);

            Assert.AreEqual(0.0, crossProduct[0].Value);
            Assert.AreEqual(0.0, crossProduct[1].Value);
            Assert.AreEqual(2.0, crossProduct[2].Value);
        }

        [TestMethod]
        public void VectorDouble_Multiply_can_perform_a_multiplication_of_a_Matrix_and_a_Vector()
        {
            /* Matrix 1
                    1   2   3
                    ---------
                1 | 2   2   2
                2 | 2   2   2
            */

            const int rowCount = 2;
            const int columnCount = 3;
            var matrix1 = CreateMatrix(rowCount, columnCount, (i, j) => i + 2);
            var vector2 = CreateVector(1, 2, 3);

            var result = Vector<double>.Multiply(matrix1, vector2);

            Assert.AreEqual(matrix1.RowCount, result.Length);
            Assert.AreEqual(12.0, result[0].Value);
            Assert.AreEqual(18.0, result[1].Value);
        }
        #endregion

        #region CosAngle
        [TestMethod]
        public void VectorDouble_CosAngle_returns_the_Zero_representation_of_a_Computable_for_two_Vectors_in_3D_that_are_orthognal()
        {
            var vector1 = CreateVector(1, 0, 0);
            var vector2 = CreateVector(0, 2, 3);

            var result = Vector<double>.CosAngle(vector1, vector2);

            Assert.AreEqual(0.0, result.Value);
        }

        [TestMethod]
        public void VectorDouble_CosAngle_returns_the_One_representation_of_a_Computable_for_two_Vectors_in_3D_that_point_in_the_same_direction()
        {
            var vector1 = CreateVector(1, 0, 0);
            var vector2 = CreateVector(10, 0, 0);

            var result = Vector<double>.CosAngle(vector1, vector2);

            Assert.AreEqual(1.0, result.Value);
        }

        [TestMethod]
        public void VectorDouble_CosAngle_returns_the_minus_One_representation_of_a_Computable_for_two_Vectors_in_3D_that_point_in_the_opposite_direction()
        {
            var vector1 = CreateVector(1, 0, 0);
            var vector2 = CreateVector(-10, 0, 0);

            var result = Vector<double>.CosAngle(vector1, vector2);

            Assert.AreEqual(-1.0, result.Value);
        }

        [TestMethod]
        public void VectorDouble_CosAngle_returns_the_negative_representation_of_a_Computable_for_two_Vectors_in_3D_that_point_in_the_opposite_direction_in_one_dimension()
        {
            var rnd = new Random(DateTime.Now.Millisecond);

            var vector1 = CreateVector(1, 0, 0);

            for (var i = 0; i < 100; i++)
            {
                var vector2 = CreateVector(-1, rnd.Next(), rnd.Next());

                var result = Vector<double>.CosAngle(vector1, vector2);

                Assert.IsTrue(result.Value < 0.0);
            }
        }

        [TestMethod]
        public void VectorDouble_CosAngle_returns_the_positive_representation_of_a_Computable_for_two_Vectors_in_3D_that_point_in_the_same_direction_in_one_dimension()
        {
            var rnd = new Random(DateTime.Now.Millisecond);

            var vector1 = CreateVector(1, 0, 0);

            for (var i = 0; i < 100; i++)
            {
                var vector2 = CreateVector(1, rnd.Next(), rnd.Next());

                var result = Vector<double>.CosAngle(vector1, vector2);

                Assert.IsTrue(result.Value > 0.0);
            }
        }
        #endregion

        #region Distance
        [TestMethod]
        public void VectorDouble_Distance_returns_the_Zero_representation_of_a_Computable_for_two_Vectors_in_3D_that_are_equal()
        {
            var vector1 = CreateVector(1, 2, 3);
            var vector2 = CreateVector(1, 2, 3);

            var result = Vector<double>.Distance(vector1, vector2);

            Assert.AreEqual(0.0, result.Value);
        }

        [TestMethod]
        public void VectorDouble_Distance_returns_the_Zero_representation_of_a_Computable_for_two_Vectors_in_10D_that_are_equal()
        {
            var vector1 = CreateVector(11, 12, 13, 10, 14, 16, 18, 17, 15, 19);
            var vector2 = CreateVector(11, 12, 13, 10, 14, 16, 18, 17, 15, 19);

            var result = Vector<double>.Distance(vector1, vector2);

            Assert.AreEqual(0.0, result.Value);
        }

        [TestMethod]
        public void VectorDouble_Distance_returns_the_norm_of_the_non_null_Vector_when_one_of_the_two_Vectors_is_null()
        {
            var vector1 = CreateVector(1, 2, 2);

            var result1 = Vector<double>.Distance(vector1, null);
            var result2 = Vector<double>.Distance(null, vector1);

            Assert.AreEqual(3.0, result1.Value);
            Assert.AreEqual(3.0, result2.Value);
        }

        [TestMethod]
        public void VectorDouble_Distance_returns_the_norm_of_the_Non_Zero_Vector_when_one_of_the_two_Vectors_is_the_Zero_Vector()
        {
            var vector1 = CreateVector(1, 2, 2);

            var result1 = Vector<double>.Distance(vector1, Vector<double>.Zero(3));
            var result2 = Vector<double>.Distance(Vector<double>.Zero(3), vector1);

            Assert.AreEqual(3.0, result1.Value);
            Assert.AreEqual(3.0, result2.Value);
        }

        [TestMethod]
        public void VectorDouble_Distance_returns_the_distance_between_two_Vectors()
        {
            var vector1 = CreateVector(10, 34, 5);
            var vector2 = CreateVector(9, 32, 3);

            var result1 = Vector<double>.Distance(vector1, vector2);
            var result2 = Vector<double>.Distance(vector2, vector1);

            Assert.AreEqual(3.0, result1.Value);
            Assert.AreEqual(3.0, result2.Value);
        }

        #endregion

        #region Equals
        [TestMethod]
        public void VectorDouble_Equals_returns_true_if_two_vectors_have_the_same_elements()
        {
            var vector1 = CreateVector(1, 2, 3);
            var vector2 = CreateVector(1, 2, 3);

            Assert.AreEqual(vector1, vector2);
        }

        [TestMethod]
        public void VectorDouble_Equals_returns_false_if_two_vectors_have_different_elements()
        {
            var vector1 = CreateVector(1, 2, 3);
            var vector2 = CreateVector(1, 2, 4);

            Assert.AreNotEqual(vector1, vector2);
        }

        [TestMethod]
        public void VectorDouble_operator_overload_for_Equals_returns_true_if_two_vectors_have_the_same_elements()
        {
            var vector1 = CreateVector(1, 2, 3);
            var vector2 = CreateVector(1, 2, 3);

            Assert.IsTrue(vector1 == vector2);
        }

        [TestMethod]
        public void VectorDouble_operator_overload_for_Equals_returns_false_if_two_vectors_have_different_elements()
        {
            var vector1 = CreateVector(1, 2, 3);
            var vector2 = CreateVector(1, 2, 4);

            Assert.IsTrue(vector1 != vector2);
        }
        #endregion

        [TestMethod, ExpectedException(typeof(InvalidOperationException))]
        public void VectorDouble_Add_throws_an_InvalidOperationException_when_the_dimensions_are_not_equal()
        {
            var vector1 = CreateVector(1, 2);
            var vector2 = CreateVector(1, 2, 3);

            vector1.Add(vector2);
        }

        [TestMethod, ExpectedException(typeof(InvalidOperationException))]
        public void VectorDouble_Subtract_throws_an_InvalidOperationException_when_the_dimensions_are_not_equal()
        {
            var vector1 = CreateVector(1, 2);
            var vector2 = CreateVector(1, 2, 3);

            vector1.Subtract(vector2);
        }

        [TestMethod, ExpectedException(typeof(InvalidOperationException))]
        public void VectorDouble_DotProduct_throws_an_InvalidOperationException_when_the_dimensions_are_not_equal()
        {
            var vector1 = CreateVector(1, 2);
            var vector2 = CreateVector(1, 2, 3);

            Vector<double>.DotProduct(vector1, vector2);
        }

        [TestMethod, ExpectedException(typeof(InvalidOperationException))]
        public void VectorDouble_CrossProduct_throws_an_InvalidOperationException_when_the_dimensions_are_not_equal_to_3()
        {
            var vector1 = CreateVector(1, 2);
            var vector2 = CreateVector(1, 2);

            Vector<double>.CrossProduct(vector1, vector2);
        }
    }
}
